package jwanalytics.agentbasedsimulations;

import java.io.IOException;
import java.net.MalformedURLException;
import java.util.Date;
import java.util.Random;

import jwanalytics.agentbasedsimulations.BuiltInSimulation.SimulationWebPage;

import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.FailingHttpStatusCodeException;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlPage;

/**
 * This is the agent which in our case occupies at all times a fixed position in the
 * simulation grid.  So the grid of agents has the same dimensions as the grid of states
 *  which are rendered with colors by BuiltInSimulationGUI.java.
 */
public class WebSiteVisitorAgent {
	
	/**
	 *  States match exactly the ids of colours on the palette created on BuiltInSimulationGUI class.	
	 */
	public SimulationWebPage pageCode = SimulationWebPage.none;
	
	/**
	 * These are hardcoded paths for the simulation pages with are built in in jwanalyticsWeb web application.
	 * The sitemap is shown on the diagram of this page: http://code.google.com/p/jwanalytics/wiki/Optimisation 
	 */
	public String[] pagePaths = {
			"none",  							    //corresponds to: BuiltInSimulation.SimulationWebPage.none and to BuiltInSimulationGUI.pallete[0] or BuiltInSimulationGUI.pallete[SimulationWebPage.none.ordinal()].
			"/jwanalyticsWeb/faces/site1.jsp",      //corresponds to: BuiltInSimulation.SimulationWebPage.site1 and to BuiltInSimulationGUI.pallete[1] or BuiltInSimulationGUI.pallete[SimulationWebPage.site1.ordinal()].
			"/jwanalyticsWeb/faces/activities.jsp", //corresponds to: BuiltInSimulation.SimulationWebPage.activities and to BuiltInSimulationGUI.pallete[2] or BuiltInSimulationGUI.pallete[SimulationWebPage.activities.ordinal()].
			"#",									//we use JavaServerFaces and the link to take to page1.jsp has actually this href value at all times.  Later we will provide a simpler JSP example although this one is not hard to follow.							
			"#",                                    //we need 3 links as there are 3 versions of the page (page1.jsp, page2.jsp, page3.jsp which can be choosen for optimizing conversion).
			"#",
			"/jwanalyticsWeb/faces/goal.jsp",       //this is the final page in orange colour (for colours of each page see BuiltInSimulationGUI).
			"unknown"
	};
	
	 /** 
	  * The domain name where this simulation attempts to reach jwanalyticsWeb is assumed to be http://localhost:8080 but you can pass an overriding value via
	  * the first command line argument when you run BuiltInSimulationGUI.
	  */
	public String protocolAndHost = "http://localhost:8080";
	
	/** This element will belong to a particular segment which will have a mean value for seconds spent on site1.jsp page. */
	public int meanSecondsOnSite1Page = 5; 
	
	/** This element will belong to a particular segment which will have a standard deviation for seconds spent on site1.jsp page. */
	public int stdDevOnSite1Page = 1;

	/** This tells how many seconds this sort of visitor will spend on site1 page. */
	public int secondsOnSite1Page = 0;
	
	/** This tells whether this type of customer will abandon the site on page 1 or not. */
	public boolean willAbandonOnSite1Page = false;
	
	/** Tells whether this visitor passes via activities.jsp page first. */
	public boolean passesViaActivitiesPageFirst = false;
	
	/** This is the number of the page page1.jsp, page2.jsp, page3.jsp that will most likely boost his propensity to purchase/convert which means going to the goal page. */
	public int pageNumber = 1;
	
	/** Tells whether this will convert to the goal page or not. */
	public boolean converts = false;
	
	/** Start of simulation time. */
	public long pageTimeCounter = -1;
	
	/** This tell whether this type of customer/visitor's web browser. */
    WebClient webClient = new WebClient(BrowserVersion.FIREFOX_2);	
    
    /** This is the last page visited by the customer or you could also call it the current page the one he is at. */
    HtmlPage currentPage = null;
    
    /** This measures our agent interest on the business or site. */
    float interest = 0.0f;
    
	public WebSiteVisitorAgent(String protocolAndHost) {
		
		this.protocolAndHost = protocolAndHost;
		
		Random generator = new Random(new Date().getTime());
		
		//will not become one element of the WebSiteVisitorAgent population with its own profile/propensities.
		double browserSort = Math.random();
		if (browserSort < 0.10) { //10% of the population runs FIREFOX2.  Among these segments we have other rules which apply.
			webClient =  new WebClient(BrowserVersion.FIREFOX_2);
			meanSecondsOnSite1Page = 5;
			stdDevOnSite1Page = 2;
		} else if (browserSort < 0.20) {
			webClient = new WebClient(BrowserVersion.FIREFOX_3);
			meanSecondsOnSite1Page = 6;
			stdDevOnSite1Page = 3;
		} else if (browserSort < 0.50) {
			webClient = new WebClient(BrowserVersion.INTERNET_EXPLORER_6);
			meanSecondsOnSite1Page = 7;
			stdDevOnSite1Page = 4;
		} else {
			webClient = new WebClient(BrowserVersion.INTERNET_EXPLORER_7);
			meanSecondsOnSite1Page = 9;
			stdDevOnSite1Page = 2;
		}
		
		secondsOnSite1Page = (int)(meanSecondsOnSite1Page + generator.nextGaussian() * stdDevOnSite1Page);
		if (secondsOnSite1Page < 0) { //this might never happen (chance not calculated, but something like one in a billion or so for having things like 5 standard deviations away from mean).
			secondsOnSite1Page = 0;
		}
		
		if (secondsOnSite1Page < meanSecondsOnSite1Page) {
			willAbandonOnSite1Page = (meanSecondsOnSite1Page - secondsOnSite1Page) * generator.nextGaussian() > 1; //the greater the difference from the mean 5 seconds on the page the greater the chance it will abandon the site.
		} else {
			willAbandonOnSite1Page = (secondsOnSite1Page - meanSecondsOnSite1Page) * generator.nextGaussian() < 1; //the smaller the difference from the mean 5 the greater the chance it will abandon the site.
		}
		
		if (!willAbandonOnSite1Page) {
			//if he spents above average on the first page (more interst) then give higher chance it will passViaActivitiesPageFirst (read more about the site since it is interested).
			//otherwise make him go straight to page1, page2 (pre-order page) where on most sites you look at prices before purchasing (so he is not product qualities oriented, but price oriented).
			if (secondsOnSite1Page > meanSecondsOnSite1Page) { 
				passesViaActivitiesPageFirst = Math.random() > 0.5;
			} else {
				passesViaActivitiesPageFirst = Math.random() < 0.3;
			}
			
			interest = secondsOnSite1Page;
			if (passesViaActivitiesPageFirst) {
				interest = 4 * Math.abs((float)generator.nextGaussian());
			}
			
			//The higher the interst, the higher the page number (page1.jsp, page2.jsp, page3.jsp) that is going to be more likely to increase his propensity to
			//proceed to the goal.jsp page completing a website conversion (purchasing a day camp package).
			if (interest < 1) {
				pageNumber = 1;				
			} else if (interest < 4) {
				pageNumber = 2;				
			} else {
				pageNumber = 3;
			}
		}
	}
	
	/** This will get a website page. */
	public HtmlPage getPage(String pageURL) throws FailingHttpStatusCodeException, MalformedURLException, IOException {
	    //webClient.addRequestHeader("Name", "Value");
		HtmlPage page = webClient.getPage(pageURL); //"http://localhost:8080/jwanalyticsWeb/faces/site1.jsp"
	    System.out.println("Getting page:" + page.getTitleText());
	    return page;
	}
	
	
	public SimulationWebPage getPageCode() {
		return pageCode;
	}
	
	public void setPageCode(SimulationWebPage pageCode) {
		this.pageCode = pageCode;
	}
	
	/** 
	 * For this simulation purposes all visitors will start on site1.jsp page spending secondsOnSite1Page  on it an then proceeding to one of these 3 options:
	 *  1. Abandon the site or (willAbandonOnSite1Page)
	 *  2. Navigate to activities.jsp going later to page1, page2, or page3.jsp (!willAbandonOnSite1Page && passesViaActivitiesPageFirst).  The choice of which page# is
	 *     presented is left up to the optimization algorithm.
	 *  3. Go directly to page1, page2, or page3.jsp (the optimized page) from the site1.jsp page.
	 * 
	 * Once arrived at page1, page2, or page3 (all that don't abandon arrive on this simulation). The visitor will either convert or not to the goal page. THis decision
	 * will be paged on whether the correct page was presented to him (pageNumber). If the correct page was presented then the chance of conversion will be higher.
	 * @param stepValue
	 */
	public void step(long stepValue) {
		if (pageCode == SimulationWebPage.none || pageTimeCounter == -1) { //first step.
			try {
				currentPage = getPage(protocolAndHost + pagePaths[SimulationWebPage.site1.ordinal()]);
			} catch(Exception e) {
				e.printStackTrace();
			}
			pageCode = SimulationWebPage.site1;
			pageTimeCounter = new Date().getTime();
		} else if (pageCode == SimulationWebPage.site1 && (new Date().getTime() - pageTimeCounter) >= (1000 * secondsOnSite1Page)) {
			if (willAbandonOnSite1Page) {
				//Start again the life of this agent as a new visitor of the same sort with same parameters (like cloning agent). 
				//So every cell in the board is a que of ever coming web site visitors of the same sort.  
				//There are 20x20 sorts, the web server does not now which one is which.
				
				restart();
			} else if (passesViaActivitiesPageFirst) {
					try {
						currentPage = getPage(protocolAndHost + pagePaths[SimulationWebPage.activities.ordinal()]); //"http://localhost:8080/jwanalyticsWeb/faces/activities.jsp"
						pageCode = SimulationWebPage.activities;
						pageTimeCounter = new Date().getTime(); //start counting time on page 2 activities.jsp
					} catch(Exception e) {
						e.printStackTrace();
					}
			} else {
					//Navigate from page1 site1.jsp straight to page1.jsp, page2.jsp, page3.jsp without passing via activities page.
					HtmlAnchor anchor = currentPage.getAnchorByHref(pagePaths[SimulationWebPage.page2.ordinal()]); //"#" just that, could take actually to page2.jsp, but alsp page1.jsp and page3.jsp.
					try {
						currentPage = (HtmlPage)anchor.click();
					} catch (IOException e) {
						e.printStackTrace();
					}
					pageCode = SimulationWebPage.page2;
					pageTimeCounter = new Date().getTime();
			}
		} else if (pageCode == SimulationWebPage.activities && (new Date().getTime() - pageTimeCounter) >= (1000 * 5)) { //everybody will spend 5 seconds on page for activities.
			//Navigate to page1.jsp, page2.jsp, page3.jsp from activities page.
			//Navigate from page1 site1.jsp straight to page1.jsp, page2.jsp, page3.jsp without passing via activities page.
			HtmlAnchor anchor = currentPage.getAnchorByHref(pagePaths[SimulationWebPage.page2.ordinal()]);
			try {
				currentPage = (HtmlPage)anchor.click();
			} catch (IOException e) {
				e.printStackTrace();
			}			
			pageCode = SimulationWebPage.page2; //we use page2 but when on this page code it could be on any of page1.jsp, page2.jsp or page3.jsp this is the page optimized by the website.
			pageTimeCounter = new Date().getTime();
		} else if (pageCode == SimulationWebPage.page2 && (new Date().getTime() - pageTimeCounter) >= (1000 * 5)) { //stay 5 seconds on any of the tree possible variations of page3. page3version1 page3version2 page3version3.
			//Decide whether we navigate to goal page or not.
			
			//Increase interest variable (interest of visitor on product/site/ordering the product whenever the visitor is sent to the right page for him).
			//We identify the open page served by the optimization algorithm by inspecting the value of the <title>page1.jsp</title> of the HTML.
			float interestCopy = interest;
			if (currentPage.getTitleText().contains("page1.jsp") && pageNumber == 1) {
				interestCopy += 1;
			} else if (currentPage.getTitleText().contains("page2.jsp") && pageNumber == 2) {
				interestCopy += 1;				
			} else if (currentPage.getTitleText().contains("page3.jsp") && pageNumber == 3) {
				interestCopy += 1;								
			}
			
			if (interestCopy * Math.random() > 2.5) { //for reasonably high interest like 5 people will purchase.
				try {
					currentPage = getPage(protocolAndHost + pagePaths[SimulationWebPage.goal.ordinal()]); // "http://localhost:8080/jwanalyticsWeb/faces/goal.jsp";
				} catch (Exception e) {
					e.printStackTrace();
				} 
				pageCode = SimulationWebPage.goal;
				pageTimeCounter = new Date().getTime(); //start counting time on page 4 goal.jsp? @TODO check if name is correct of jsp page.
			} else {
				restart();
			}
		} else if (pageCode == SimulationWebPage.goal && (new Date().getTime() - pageTimeCounter) >= (1000 * 6)) { //spend 6 seconds on goal page so we can see it on simulation board.
			restart();
		}
	}
	
	
	private void restart() {
		try {
			//This is what speeds up the tempo of simulations by not making it have to wait for a session to be closed to have the data from the session
			//to be used by the predictiveAnalytics real time data mining.
			getPage(protocolAndHost + "/jwanalyticsWeb/faces/closeSession.jsp"); // "http://localhost:8080/jwanalyticsWeb/faces/closeSession.jsp";
		} catch(Exception e) {
			e.printStackTrace();
		}
		currentPage = null;
		pageCode = SimulationWebPage.none;
		pageTimeCounter = -1; //always restart life as an agent of the same sort.  Abandon session late while on page 3.		
	}
	
}
